#include "ByteStream.h"
#include <stdio.h>
#include <string.h>

ByteStream::ByteStream(): segmentlist(0), streamsize(0)
{
	read.segment = 0;
	read.segmentpos = 0;

	peek.segment = 0;
	peek.segmentpos = 0;
}

ByteStream::~ByteStream()
{
	Clear();
}

unsigned int ByteStream::Size() const
{
	return streamsize;
}

void ByteStream::Clear()
{
	ByteSegment *headsegment = segmentlist;

	if (headsegment)
	{
		ByteSegment *segment = headsegment;

		do
		{
			unsigned char *delsegment = (unsigned char*)segment;

			segment = segment->next;

			delete[] delsegment;
		} 
		while(headsegment != segment);
	}

	segmentlist = 0;
	read.segment = 0;
	read.segmentpos = 0;
	peek.segment = 0;
	peek.segmentpos = 0;
	streamsize = 0;
}

ByteStream::ByteSegment* ByteStream::NextSegment(ByteStream::ByteSegment *segment)
{
	// If last segment
	if (segmentlist->previous == segment)
	{
		segment = 0;
	}
	else
	{
		segment = segment->next;
	}

	return segment;
}

void ByteStream::RemoveFirstSegment()
{
	unsigned char *delsegment = (unsigned char*)segmentlist;

	// If last segment
	if (segmentlist->previous == segmentlist)
	{
		segmentlist = 0;
	}
	else
	{
		ByteSegment *next = segmentlist->next;
		ByteSegment *last = segmentlist->previous;

		next->previous = last;
		last->next = next;

		segmentlist = next;
	}

	// Update normal read's segment to first segment
	read.segment = segmentlist;

	// Update peek segment to first segment if it got behind the normal read
	if (peek.segment == (ByteSegment*)delsegment)
		peek.segment = segmentlist;
	

	delete[] delsegment;
}

void ByteStream::AddSegment(ByteSegment *segment)
{
	if (segmentlist == 0)
	{
		segmentlist = segment;
		segment->next = segment->previous = segment;

		// Set normal reader and peek reader starting segments
		peek.segment = segment;
		read.segment = segment;
	}
	else
	{
		ByteSegment *lastsegment = segmentlist->previous;
		ByteSegment *firstsegment = segmentlist;

		lastsegment->next = segment;
		firstsegment->previous = segment;

		segment->previous = lastsegment;
		segment->next = firstsegment;
	}
}

void ByteStream::Write(const void *data, unsigned int size)
{
	WriteRaw(data, size);
}

// Read from the buffer, and write it into the bytestream
void ByteStream::WriteRaw(const void *data, unsigned int size)
{
	unsigned char* segmentdata = new unsigned char[size + sizeof(ByteSegment)];
	unsigned char* streamdata = segmentdata + sizeof(ByteSegment);
	ByteSegment *segment = (ByteSegment*)segmentdata;

	streamsize += size;

	segment->size = size;
	memcpy(streamdata, data, size);

	AddSegment(segment);
}

unsigned int ByteStream::Read(void *data, unsigned int size)
{
	return ReadRaw(data, size);
}

unsigned int ByteStream::Peek(void *data, unsigned int size)
{
	return PeekRaw(data, size);
}

unsigned int ByteStream::ReadRaw(void *data, unsigned int size)
{
	unsigned int totalread = ReadViaReader(&read, data, size, true);

	streamsize -= totalread;

	peek.segment = read.segment;
	peek.segmentpos = read.segmentpos;

	return totalread;
}

unsigned int ByteStream::PeekRaw(void *data, unsigned int size)
{
	return ReadViaReader(&peek, data, size, false);
}


unsigned int ByteStream::ReadViaReader(Reader *reader, void *data, unsigned int size, bool removesegment)
{
	unsigned char *bytedata = (unsigned char*)data;
	unsigned int totalread = 0;

	if (!reader->segment)
		return 0;

	while(size)
	{
		ByteSegment *segment = reader->segment;
		unsigned int segmentpos = reader->segmentpos;
		unsigned int leftsize = segment->size - segmentpos;
		unsigned int readsize = leftsize;

		unsigned char *streamdata = (unsigned char*)segment + sizeof(ByteSegment) + segmentpos;
		if (readsize > size)
			readsize = size;

		memcpy(bytedata, streamdata, readsize);
		bytedata += readsize;
		totalread += readsize;
		size -= readsize;

		reader->segmentpos += readsize;

		if (reader->segmentpos >= segment->size)
		{
			ByteSegment *nextsegment = NextSegment(segment);

			reader->segmentpos = 0;

			if (removesegment)
				RemoveFirstSegment();

			reader->segment = nextsegment;
		}

		if (reader->segment == 0)
			break;
	}

	return totalread;
}




// Read data from a file into the bytestream
void ByteStream::ReadFromFile(const char* filename)
{
	FILE *f = fopen(filename, "rb");

	if(f == 0)
		throw "ByteStream: Could not read file";

	unsigned int filesize;

	fseek(f, 0, SEEK_END);
	filesize = ftell(f);
	fseek(f, 0, SEEK_SET);


	unsigned char* segmentdata = new unsigned char[filesize + sizeof(ByteSegment)];
	unsigned char* streamdata = segmentdata + sizeof(ByteSegment);
	ByteSegment *segment = (ByteSegment*)segmentdata;

	segment->size = filesize;
	fread(streamdata, sizeof(unsigned char), filesize, f);

	AddSegment(segment);

	fclose(f);
}


// Write the bytestream data to a file
void ByteStream::WriteToFile(const char *filename)
{
	unsigned char buffer[1024];
	FILE *f = fopen(filename, "wb");

	if(f == 0)
		throw "ByteStream: Could not write to file";

	while(1)
	{
		unsigned int readbytes = ReadRaw(buffer, sizeof(buffer));

		if (readbytes == 0)
			break;

		fwrite(buffer, sizeof(unsigned char), readbytes, f);
	}

	fclose(f);
}